// Copyright (c) Microsoft Open Technologies, Inc.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CodeGeneration
{
    /// <summary>
    /// A language agnostic factory for creating syntax nodes.
    /// 
    /// This API can be used to create language specific syntax nodes that are semantically similar between languages.
    /// </summary>
    public abstract class SyntaxGenerator : ILanguageService
    {
        /// <summary>
        /// Gets the <see cref="SyntaxGenerator"/> for the specified language.
        /// </summary>
        public static SyntaxGenerator GetGenerator(Workspace workspace, string language)
        {
            return workspace.Services.GetLanguageServices(language).GetService<SyntaxGenerator>();
        }

        #region Declarations

        /// <summary>
        /// Creates a field declaration.
        /// </summary>
        public abstract SyntaxNode FieldDeclaration(
            string name,
            SyntaxNode type,
            Accessibility accessibility = Accessibility.NotApplicable,
            SymbolModifiers modifiers = default(SymbolModifiers),
            SyntaxNode initializer = null);

        /// <summary>
        /// Creates a field declaration matching an existing field symbol.
        /// </summary>
        public SyntaxNode FieldDeclaration(IFieldSymbol field, SyntaxNode initializer = null)
        {
            return FieldDeclaration(
                field.Name,
                TypeExpression(field.Type),
                field.DeclaredAccessibility,
                SymbolModifiers.From(field),
                initializer);
        }

        protected static SymbolModifiers fieldModifiers = SymbolModifiers.Const | SymbolModifiers.ReadOnly | SymbolModifiers.Static;
        protected static SymbolModifiers methodModifiers = SymbolModifiers.Abstract | SymbolModifiers.Async | SymbolModifiers.New | SymbolModifiers.Override | SymbolModifiers.Partial | SymbolModifiers.Sealed | SymbolModifiers.Static | SymbolModifiers.Virtual;
        protected static SymbolModifiers constructorModifers = SymbolModifiers.Static;
        protected static SymbolModifiers propertyModifiers = SymbolModifiers.Abstract | SymbolModifiers.New | SymbolModifiers.Override | SymbolModifiers.ReadOnly | SymbolModifiers.Sealed | SymbolModifiers.Static | SymbolModifiers.Virtual;
        protected static SymbolModifiers indexerModifiers = SymbolModifiers.Abstract | SymbolModifiers.New | SymbolModifiers.Override | SymbolModifiers.ReadOnly | SymbolModifiers.Sealed | SymbolModifiers.Static | SymbolModifiers.Virtual;
        protected static SymbolModifiers typeModifiers = SymbolModifiers.Abstract | SymbolModifiers.Partial | SymbolModifiers.Sealed | SymbolModifiers.Static;

        /// <summary>
        /// Creates a method declaration.
        /// </summary>
        public abstract SyntaxNode MethodDeclaration(
            string name,
            IEnumerable<SyntaxNode> parameters = null,
            IEnumerable<string> typeParameters = null,
            SyntaxNode returnType = null,
            Accessibility accessibility = Accessibility.NotApplicable,
            SymbolModifiers modifiers = default(SymbolModifiers),
            IEnumerable<SyntaxNode> statements = null);

        /// <summary>
        /// Creates a method declaration matching an existing method symbol.
        /// </summary>
        public SyntaxNode MethodDeclaration(IMethodSymbol method, IEnumerable<SyntaxNode> statements = null)
        {
            var decl = MethodDeclaration(
                method.Name,
                parameters: method.Parameters.Select(p => ParameterDeclaration(p)),
                returnType: TypeExpression(method.ReturnType),
                accessibility: method.DeclaredAccessibility,
                modifiers: SymbolModifiers.From(method),
                statements: statements);

            if (method.TypeParameters.Length > 0)
            {
                decl = this.WithTypeParametersAndConstraints(decl, method.TypeParameters);
            }

            return decl;
        }

        /// <summary>
        /// Creates a parameter declaration.
        /// </summary>
        public abstract SyntaxNode ParameterDeclaration(
            string name,
            SyntaxNode type = null,
            SyntaxNode initializer = null,
            RefKind refKind = RefKind.None);

        /// <summary>
        /// Creates a parameter declaration matching an existing parameter symbol.
        /// </summary>
        public SyntaxNode ParameterDeclaration(IParameterSymbol symbol, SyntaxNode initializer = null)
        {
            return ParameterDeclaration(
                symbol.Name,
                TypeExpression(symbol.Type),
                initializer,
                symbol.RefKind);
        }

        /// <summary>
        /// Creates a property declaration.
        /// </summary>
        public abstract SyntaxNode PropertyDeclaration(
            string name,
            SyntaxNode type,
            Accessibility accessibility = Accessibility.NotApplicable,
            SymbolModifiers modifiers = default(SymbolModifiers),
            IEnumerable<SyntaxNode> getterStatements = null,
            IEnumerable<SyntaxNode> setterStatements = null);

        /// <summary>
        /// Creates a property declaration using an existing property symbol as a signature.
        /// </summary>
        public SyntaxNode PropertyDeclaration(IPropertySymbol property, IEnumerable<SyntaxNode> getterStatements = null, IEnumerable<SyntaxNode> setterStatements = null)
        {
            return PropertyDeclaration(
                    property.Name,
                    TypeExpression(property.Type),
                    property.DeclaredAccessibility,
                    SymbolModifiers.From(property),
                    getterStatements,
                    setterStatements);
        }

        /// <summary>
        /// Creates an indexer declaration.
        /// </summary>
        public abstract SyntaxNode IndexerDeclaration(
            IEnumerable<SyntaxNode> parameters,
            SyntaxNode type,
            Accessibility accessibility = Accessibility.NotApplicable,
            SymbolModifiers modifiers = default(SymbolModifiers),
            IEnumerable<SyntaxNode> getterStatements = null,
            IEnumerable<SyntaxNode> setterStatements = null);

        /// <summary>
        /// Creates an indexer declaration matching an existing indexer symbol.
        /// </summary>
        public SyntaxNode IndexerDeclaration(IPropertySymbol indexer, IEnumerable<SyntaxNode> getterStatements = null, IEnumerable<SyntaxNode> setterStatements = null)
        {
            return IndexerDeclaration(
                indexer.Parameters.Select(p => this.ParameterDeclaration(p)),
                TypeExpression(indexer.Type),
                indexer.DeclaredAccessibility,
                SymbolModifiers.From(indexer),
                getterStatements,
                setterStatements);
        }

        /// <summary>
        /// Creates a constructor declaration.
        /// </summary>
        public abstract SyntaxNode ConstructorDeclaration(
            string containingTypeName = null,
            IEnumerable<SyntaxNode> parameters = null,
            Accessibility accessibility = Accessibility.NotApplicable,
            SymbolModifiers modifiers = default(SymbolModifiers),
            IEnumerable<SyntaxNode> baseConstructorArguments = null,
            IEnumerable<SyntaxNode> statements = null);

        /// <summary>
        /// Create a constructor declaration using 
        /// </summary>
        public SyntaxNode ConstructorDeclaration(
            IMethodSymbol constructorMethod,
            IEnumerable<SyntaxNode> baseConstructorArguments = null,
            IEnumerable<SyntaxNode> statements = null)
        {
            return ConstructorDeclaration(
                constructorMethod.ContainingType != null ? constructorMethod.ContainingType.Name : "New",
                constructorMethod.Parameters.Select(p => ParameterDeclaration(p)),
                constructorMethod.DeclaredAccessibility,
                SymbolModifiers.From(constructorMethod),
                baseConstructorArguments,
                statements);
        }

        /// <summary>
        /// Converts method, property and indexer declarations into public interface implementations.
        /// This is equivalent to an implicit C# interface implementation (you can access it via the interface or directly via the named member.)
        /// </summary>
        public abstract SyntaxNode AsPublicInterfaceImplementation(SyntaxNode declaration, SyntaxNode interfaceType);

        /// <summary>
        /// Converts method, property and indexer declarations into private interface implementations.
        /// This is equivalent to a C# explicit interface implementation (you can declare it for access via the interface, but cannot call it directly).
        /// </summary>
        public abstract SyntaxNode AsPrivateInterfaceImplementation(SyntaxNode declaration, SyntaxNode interfaceType);

        /// <summary>
        /// Creates a class declaration.
        /// </summary>
        public abstract SyntaxNode ClassDeclaration(
            string name,
            IEnumerable<string> typeParameters = null,
            Accessibility accessibility = Accessibility.NotApplicable,
            SymbolModifiers modifiers = default(SymbolModifiers),
            SyntaxNode baseType = null,
            IEnumerable<SyntaxNode> interfaceTypes = null,
            IEnumerable<SyntaxNode> members = null);

        /// <summary>
        /// Creates a struct declaration.
        /// </summary>
        public abstract SyntaxNode StructDeclaration(
            string name,
            IEnumerable<string> typeParameters = null,
            Accessibility accessibility = Accessibility.NotApplicable,
            SymbolModifiers modifiers = default(SymbolModifiers),
            IEnumerable<SyntaxNode> interfaceTypes = null,
            IEnumerable<SyntaxNode> members = null);

        /// <summary>
        /// Creates a interface declaration.
        /// </summary>
        public abstract SyntaxNode InterfaceDeclaration(
            string name,
            IEnumerable<string> typeParameters = null,
            Accessibility accessibility = Accessibility.NotApplicable,
            IEnumerable<SyntaxNode> interfaceTypes = null,
            IEnumerable<SyntaxNode> members = null);

        /// <summary>
        /// Creates an enum declaration.
        /// </summary>
        public abstract SyntaxNode EnumDeclaration(
            string name,
            Accessibility accessibility = Accessibility.NotApplicable,
            IEnumerable<SyntaxNode> members = null);

        /// <summary>
        /// Creates an enum member
        /// </summary>
        public abstract SyntaxNode EnumMember(string name, SyntaxNode expression = null);

        /// <summary>
        /// Creates a declaration matching an existing symbol.
        /// </summary>
        public SyntaxNode Declaration(ISymbol symbol)
        {
            switch (symbol.Kind)
            {
                case SymbolKind.Field:
                    return FieldDeclaration((IFieldSymbol)symbol);

                case SymbolKind.Property:
                    var property = (IPropertySymbol)symbol;
                    if (property.IsIndexer)
                    {
                        return IndexerDeclaration(property);
                    }
                    else
                    {
                        return PropertyDeclaration(property);
                    }

                case SymbolKind.Method:
                    var method = (IMethodSymbol)symbol;
                    switch (method.MethodKind)
                    {
                        case MethodKind.Constructor:
                        case MethodKind.SharedConstructor:
                            return ConstructorDeclaration(method);

                        case MethodKind.Ordinary:
                            return MethodDeclaration(method);
                    }

                    break;

                case SymbolKind.Parameter:
                    return ParameterDeclaration((IParameterSymbol)symbol);

                case SymbolKind.NamedType:
                    var type = (INamedTypeSymbol)symbol;
                    SyntaxNode declaration = null;

                    switch (type.TypeKind)
                    {
                        case TypeKind.Class:
                            declaration = ClassDeclaration(
                                type.Name,
                                accessibility: type.DeclaredAccessibility,
                                modifiers: SymbolModifiers.From(type),
                                baseType: TypeExpression(type.BaseType),
                                interfaceTypes: type.Interfaces != null ? type.Interfaces.Select(i => TypeExpression(i)) : null,
                                members: type.GetMembers().Select(m => Declaration(m)));
                            break;
                        case TypeKind.Struct:
                            declaration = StructDeclaration(
                                type.Name,
                                accessibility: type.DeclaredAccessibility,
                                modifiers: SymbolModifiers.From(type),
                                interfaceTypes: type.Interfaces != null ? type.Interfaces.Select(i => TypeExpression(i)) : null,
                                members: type.GetMembers().Select(m => Declaration(m)));
                            break;
                        case TypeKind.Interface:
                            declaration = InterfaceDeclaration(
                                type.Name,
                                accessibility: type.DeclaredAccessibility,
                                interfaceTypes: type.Interfaces != null ? type.Interfaces.Select(i => TypeExpression(i)) : null,
                                members: type.GetMembers().Select(m => Declaration(m)));
                            break;
                        case TypeKind.Enum:
                            declaration = EnumDeclaration(
                                type.Name,
                                accessibility: type.DeclaredAccessibility,
                                members: type.GetMembers().Select(m => Declaration(m)));
                            break;
                    }

                    if (declaration != null)
                    {
                        return WithTypeParametersAndConstraints(declaration, type.TypeParameters);
                    }

                    break;
            }

            throw new ArgumentException("Symbol cannot be converted to a declaration");
        }

        private SyntaxNode WithTypeParametersAndConstraints(SyntaxNode declaration, ImmutableArray<ITypeParameterSymbol> typeParameters)
        {
            if (typeParameters.Length > 0)
            {
                declaration = WithTypeParameters(declaration, typeParameters.Select(tp => tp.Name));

                foreach (var tp in typeParameters)
                {
                    if (tp.HasConstructorConstraint || tp.HasReferenceTypeConstraint || tp.HasValueTypeConstraint || tp.ConstraintTypes.Length > 0)
                    {
                        declaration = this.WithTypeConstraint(declaration, tp.Name,
                            kinds: (tp.HasConstructorConstraint ? SpecialTypeConstraintKind.Constructor : SpecialTypeConstraintKind.None)
                                   | (tp.HasReferenceTypeConstraint ? SpecialTypeConstraintKind.ReferenceType : SpecialTypeConstraintKind.None)
                                   | (tp.HasValueTypeConstraint ? SpecialTypeConstraintKind.ValueType : SpecialTypeConstraintKind.None),
                            types: tp.ConstraintTypes.Select(t => TypeExpression(t)));
                    }
                }
            }

            return declaration;
        }

        /// <summary>
        /// Converts a declaration (method, class, etc) into a declaration with type parameters.
        /// </summary>
        public abstract SyntaxNode WithTypeParameters(SyntaxNode declaration, IEnumerable<string> typeParameterNames);

        /// <summary>
        /// Converts a declaration (method, class, etc) into a declaration with type parameters.
        /// </summary>
        public SyntaxNode WithTypeParameters(SyntaxNode declaration, params string[] typeParameterNames)
        {
            return WithTypeParameters(declaration, (IEnumerable<string>)typeParameterNames);
        }

        /// <summary>
        /// Adds a type constraint to a type parameter of a declaration.
        /// </summary>
        public abstract SyntaxNode WithTypeConstraint(SyntaxNode declaration, string typeParameterName, SpecialTypeConstraintKind kinds, IEnumerable<SyntaxNode> types = null);

        /// <summary>
        /// Adds a type constraint to a type parameter of a declaration.
        /// </summary>
        public SyntaxNode WithTypeConstraint(SyntaxNode declaration, string typeParameterName, SpecialTypeConstraintKind kinds, params SyntaxNode[] types)
        {
            return WithTypeConstraint(declaration, typeParameterName, kinds, (IEnumerable<SyntaxNode>)types);
        }

        /// <summary>
        /// Adds a type constraint to a type parameter of a declaration.
        /// </summary>
        public SyntaxNode WithTypeConstraint(SyntaxNode declaration, string typeParameterName, params SyntaxNode[] types)
        {
            return WithTypeConstraint(declaration, typeParameterName, SpecialTypeConstraintKind.None, (IEnumerable<SyntaxNode>)types);
        }

        /// <summary>
        /// Creates a namespace declaration.
        /// </summary>
        public abstract SyntaxNode NamespaceDeclaration(SyntaxNode name, IEnumerable<SyntaxNode> declarations);

        /// <summary>
        /// Creates a namespace declaration.
        /// </summary>
        public SyntaxNode NamespaceDeclaration(SyntaxNode name, params SyntaxNode[] declarations)
        {
            return NamespaceDeclaration(name, (IEnumerable<SyntaxNode>)declarations);
        }

        /// <summary>
        /// Creates a namespace declaration.
        /// </summary>
        public SyntaxNode NamespaceDeclaration(string name, IEnumerable<SyntaxNode> declarations)
        {
            return NamespaceDeclaration(DottedName(name), declarations);
        }

        /// <summary>
        /// Creates a namespace declaration.
        /// </summary>
        public SyntaxNode NamespaceDeclaration(string name, params SyntaxNode[] declarations)
        {
            return NamespaceDeclaration(DottedName(name), (IEnumerable<SyntaxNode>)declarations);
        }

        /// <summary>
        /// Creates a compilation unit declaration
        /// </summary>
        public abstract SyntaxNode CompilationUnit(IEnumerable<SyntaxNode> declarations = null);

        /// <summary>
        /// Creates a compilation unit declaration
        /// </summary>
        public SyntaxNode CompilationUnit(params SyntaxNode[] declarations)
        {
            return CompilationUnit((IEnumerable<SyntaxNode>)declarations);
        }

        /// <summary>
        /// Creates a namespace import declaration.
        /// </summary>
        public abstract SyntaxNode NamespaceImportDeclaration(SyntaxNode name);

        /// <summary>
        /// Creates a namespace import declaration.
        /// </summary>
        public SyntaxNode NamespaceImportDeclaration(string name)
        {
            return NamespaceImportDeclaration(DottedName(name));
        }

        /// <summary>
        /// Creates an attribute.
        /// </summary>
        public abstract SyntaxNode Attribute(SyntaxNode name, IEnumerable<SyntaxNode> attributeArguments = null);

        /// <summary>
        /// Creates an attribute.
        /// </summary>
        public SyntaxNode Attribute(string name, IEnumerable<SyntaxNode> attributeArguments = null)
        {
            return Attribute(DottedName(name), attributeArguments);
        }

        /// <summary>
        /// Creates an attribute.
        /// </summary>
        public SyntaxNode Attribute(string name, params SyntaxNode[] attributeArguments)
        {
            return Attribute(name, (IEnumerable<SyntaxNode>)attributeArguments);
        }

        /// <summary>
        /// Creates an attribute matching existing attribute data.
        /// </summary>
        public SyntaxNode Attribute(AttributeData attribute)
        {
            return Attribute(
                name: TypeExpression(attribute.AttributeClass),
                attributeArguments:
                    attribute.ConstructorArguments.Select(a => this.AttributeArgument(this.LiteralExpression(a.Value)))
                    .Concat(attribute.NamedArguments.Select(n => this.AttributeArgument(n.Key, this.LiteralExpression(n.Value.Value)))));
        }

        private IEnumerable<SyntaxNode> GetSymbolAttributes(ISymbol symbol)
        {
            return symbol.GetAttributes().Select(a => Attribute(a));
        }

        /// <summary>
        /// Creates an attribute argument.
        /// </summary>
        public abstract SyntaxNode AttributeArgument(string name, SyntaxNode expression);

        /// <summary>
        /// Creates an attribute argument.
        /// </summary>
        public SyntaxNode AttributeArgument(SyntaxNode expression)
        {
            return AttributeArgument(null, expression);
        }

        /// <summary>
        /// Creates a new instance of a declaration node with the specified attributes added.
        /// </summary>
        public abstract SyntaxNode AddAttributes(SyntaxNode declaration, IEnumerable<SyntaxNode> attributes);

        /// <summary>
        /// Creates a new instance of a declaration node with the specified attributes added.
        /// </summary>
        public SyntaxNode AddAttributes(SyntaxNode declaration, params SyntaxNode[] attributes)
        {
            return AddAttributes(declaration, (IEnumerable<SyntaxNode>)attributes);
        }

        /// <summary>
        /// Creates a new instance of a method declaration node with return attributes added.
        /// </summary>
        public abstract SyntaxNode AddReturnAttributes(SyntaxNode methodDeclaration, IEnumerable<SyntaxNode> attributes);

        /// <summary>
        /// Creates a new instance of a method declaration node with return attributes added.
        /// </summary>
        public SyntaxNode AddReturnAttributes(SyntaxNode methodDeclaration, params SyntaxNode[] attributes)
        {
            return AddReturnAttributes(methodDeclaration, (IEnumerable<SyntaxNode>)attributes);
        }

        #endregion

        #region Statements
        /// <summary>
        /// Creates statement that allows an expression to execute in a statement context.
        /// This is typically an invocation or assignment expression.
        /// </summary>
        /// <param name="expression">The expression that is to be executed. This is usually a method invocation expression.</param>
        public abstract SyntaxNode ExpressionStatement(SyntaxNode expression);

        /// <summary>
        /// Creates a statement that can be used to return a value from a method body.
        /// </summary>
        /// <param name="expression">An optional expression that can be returned.</param>
        public abstract SyntaxNode ReturnStatement(SyntaxNode expression = null);

        /// <summary>
        /// Creates a statement that can be used to throw and exception.
        /// </summary>
        /// <param name="expression">An optional expression that can be thrown.</param>
        public abstract SyntaxNode ThrowStatement(SyntaxNode expression = null);

        /// <summary>
        /// Creates a statement that declares a single local variable.
        /// </summary>
        public abstract SyntaxNode LocalDeclarationStatement(SyntaxNode type, string identifier, SyntaxNode initializer = null, bool isConst = false);

        /// <summary>
        /// Creates a statement that declares a single local variable.
        /// </summary>
        public SyntaxNode LocalDeclarationStatement(ITypeSymbol type, string name, SyntaxNode initializer = null, bool isConst = false)
        {
            return LocalDeclarationStatement(TypeExpression(type), name, initializer, isConst);
        }

        /// <summary>
        /// Creates a statement that declares a single local variable.
        /// </summary>
        public SyntaxNode LocalDeclarationStatement(string name, SyntaxNode initializer)
        {
            return LocalDeclarationStatement((SyntaxNode)null, name, initializer);
        }

        /// <summary>
        /// Creates a if-then-else statement or equivalent 
        /// </summary>
        /// <param name="condition">The if-condition.</param>
        /// <param name="trueStatements">The statements that are executed if the condition is true.</param>
        /// <param name="falseStatements">The statements that are executed if the condition is false.</param>
        public abstract SyntaxNode IfStatement(SyntaxNode condition, IEnumerable<SyntaxNode> trueStatements, IEnumerable<SyntaxNode> falseStatements = null);

        /// <summary>
        /// Creates a if-then-else statement or equivalent 
        /// </summary>
        /// <param name="condition">The if-condition.</param>
        /// <param name="trueStatements">The statements that are executed if the condition is true.</param>
        /// <param name="falseStatement">A single statement that is executed if the condition is false. Typically this anther if-then-else statement.</param>
        public SyntaxNode IfStatement(SyntaxNode condition, IEnumerable<SyntaxNode> trueStatements, SyntaxNode falseStatement)
        {
            return IfStatement(condition, trueStatements, new[] { falseStatement });
        }

        /// <summary>
        /// Creates a switch statement that branches to a sections based on the value of the specified expression.
        /// </summary>
        public abstract SyntaxNode SwitchStatement(SyntaxNode expression, IEnumerable<SyntaxNode> sections);

        /// <summary>
        /// Creates a switch statement that branches to a section based on the value of the specified expression.
        /// </summary>
        public SyntaxNode SwitchStatement(SyntaxNode expression, params SyntaxNode[] sections)
        {
            return SwitchStatement(expression, (IEnumerable<SyntaxNode>)sections);
        }

        /// <summary>
        /// Creates a section for a switch statement.
        /// </summary>
        public abstract SyntaxNode SwitchSection(IEnumerable<SyntaxNode> caseExpressions, IEnumerable<SyntaxNode> statements);

        /// <summary>
        /// Creates a single-case section a switch statement.
        /// </summary>
        public SyntaxNode SwitchSection(SyntaxNode caseExpression, IEnumerable<SyntaxNode> statements)
        {
            return SwitchSection(new[] { caseExpression }, statements);
        }

        /// <summary>
        /// Creates a default section for a switch statement.
        /// </summary>
        public abstract SyntaxNode DefaultSwitchSection(IEnumerable<SyntaxNode> statements);

        /// <summary>
        /// Create a statement that exits a switch statement and continues after it.
        /// </summary>
        public abstract SyntaxNode ExitSwitchStatement();

        /// <summary>
        /// Creates a statement that represents a using-block pattern.
        /// </summary>
        public abstract SyntaxNode UsingStatement(SyntaxNode type, string name, SyntaxNode expression, IEnumerable<SyntaxNode> statements);

        /// <summary>
        /// Creates a statement that represents a using-block pattern.
        /// </summary>
        public SyntaxNode UsingStatement(string name, SyntaxNode expression, IEnumerable<SyntaxNode> statements)
        {
            return UsingStatement(null, name, expression, statements);
        }

        /// <summary>
        /// Creates a statement that represents a using-block pattern.
        /// </summary>
        public abstract SyntaxNode UsingStatement(SyntaxNode expression, IEnumerable<SyntaxNode> statements);

        /// <summary>
        /// Creates a try-catch or try-catch-finally statement.
        /// </summary>
        public abstract SyntaxNode TryCatchStatement(IEnumerable<SyntaxNode> tryStatements, IEnumerable<SyntaxNode> catchClauses, IEnumerable<SyntaxNode> finallyStatements = null);

        /// <summary>
        /// Creates a try-catch or try-catch-finally statement.
        /// </summary>
        public SyntaxNode TryCatchStatement(IEnumerable<SyntaxNode> tryStatements, params SyntaxNode[] catchClauses)
        {
            return TryCatchStatement(tryStatements, (IEnumerable<SyntaxNode>)catchClauses);
        }

        /// <summary>
        /// Creates a try-finally statement.
        /// </summary>
        public SyntaxNode TryFinallyStatement(IEnumerable<SyntaxNode> tryStatements, IEnumerable<SyntaxNode> finallyStatements)
        {
            return TryCatchStatement(tryStatements, catchClauses: null, finallyStatements: finallyStatements);
        }

        /// <summary>
        /// Creates a catch-clause.
        /// </summary>
        public abstract SyntaxNode CatchClause(SyntaxNode type, string identifier, IEnumerable<SyntaxNode> statements);

        /// <summary>
        /// Creates a catch-clause.
        /// </summary>
        public SyntaxNode CatchClause(ITypeSymbol type, string identifer, IEnumerable<SyntaxNode> statements)
        {
            return CatchClause(TypeExpression(type), identifer, statements);
        }

        /// <summary>
        /// Creates a while-loop statement
        /// </summary>
        public abstract SyntaxNode WhileStatement(SyntaxNode condition, IEnumerable<SyntaxNode> statements);

        #endregion

        #region Expressions
        /// <summary>
        /// An expression that represents the default value of a type.
        /// This is typically a null value for reference types or a zero-filled value for value types.
        /// </summary>
        public abstract SyntaxNode DefaultExpression(SyntaxNode type);
        public abstract SyntaxNode DefaultExpression(ITypeSymbol type);

        /// <summary>
        /// Creates an expression that denotes the containing methods this-parameter.
        /// </summary>
        public abstract SyntaxNode ThisExpression();

        /// <summary>
        /// Creates an expression that denotes the containing methods base-parameter.
        /// </summary>
        public abstract SyntaxNode BaseExpression();

        /// <summary>
        /// Creates a literal expression. This is typically numeric primitives, strings or chars.
        /// </summary>
        public abstract SyntaxNode LiteralExpression(object value);

        /// <summary>
        /// Creates an expression that denotes the boolean false literal.
        /// </summary>
        public SyntaxNode FalseLiteralExpression()
        {
            return LiteralExpression(false);
        }

        /// <summary>
        /// Creates an expression that denotes the boolean true literal.
        /// </summary>
        public SyntaxNode TrueLiteralExpression()
        {
            return LiteralExpression(true);
        }

        /// <summary>
        /// Creates an expression that denotes the null literal.
        /// </summary>
        public SyntaxNode NullLiteralExpression()
        {
            return LiteralExpression(null);
        }

        /// <summary>
        /// Creates an expression that denotes a simple identifier name.
        /// </summary>
        /// <param name="identifier"></param>
        /// <returns></returns>
        public abstract SyntaxNode IdentifierName(string identifier);

        /// <summary>
        /// Creates an expression that denotes a generic identifier name.
        /// </summary>
        public abstract SyntaxNode GenericName(string identifier, IEnumerable<SyntaxNode> typeArguments);

        /// <summary>
        /// Creates an expression that denotes a generic identifier name.
        /// </summary>
        public SyntaxNode GenericName(string identifier, IEnumerable<ITypeSymbol> typeArguments)
        {
            return GenericName(identifier, typeArguments.Select(ta => TypeExpression(ta)));
        }

        /// <summary>
        /// Creates an expression that denotes a generic identifier name.
        /// </summary>
        public SyntaxNode GenericName(string identifier, params SyntaxNode[] typeArguments)
        {
            return GenericName(identifier, (IEnumerable<SyntaxNode>)typeArguments);
        }

        /// <summary>
        /// Creates an expression that denotes a generic identifier name.
        /// </summary>
        public SyntaxNode GenericName(string identifier, params ITypeSymbol[] typeArguments)
        {
            return GenericName(identifier, (IEnumerable<ITypeSymbol>)typeArguments);
        }

        /// <summary>
        /// Converts an expression that ends in a name into an expression that ends in a generic name.
        /// If the expression already ends in a generic name, the new type arguments are used instead.
        /// </summary>
        public abstract SyntaxNode WithTypeArguments(SyntaxNode expression, IEnumerable<SyntaxNode> typeArguments);

        /// <summary>
        /// Converts an expression that ends in a name into an expression that ends in a generic name.
        /// If the expression already ends in a generic name, the new type arguments are used instead.
        /// </summary>
        public SyntaxNode WithTypeArguments(SyntaxNode expression, params SyntaxNode[] typeArguments)
        {
            return WithTypeArguments(expression, (IEnumerable<SyntaxNode>)typeArguments);
        }

        /// <summary>
        /// Creates a name expression that denotes a qualified name. 
        /// The left operand can be any name expression.
        /// The right operand can be either and identifier or generic name.
        /// </summary>
        public abstract SyntaxNode QualifiedName(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates a name expression from a dotted name string.
        /// </summary>
        public SyntaxNode DottedName(string dottedName)
        {
            if (dottedName == null)
            {
                throw new ArgumentNullException("dottedName");
            }

            var parts = dottedName.Split(dotSeparator);

            SyntaxNode name = null;
            foreach (var part in parts)
            {
                if (name == null)
                {
                    name = IdentifierName(part);
                }
                else
                {
                    name = QualifiedName(name, IdentifierName(part));
                }
            }

            return name;
        }

        private static readonly char[] dotSeparator = new char[] { '.' };

        /// <summary>
        /// Creates an expression that denotes a type.
        /// </summary>
        public abstract SyntaxNode TypeExpression(ITypeSymbol typeSymbol);

        /// <summary>
        /// Creates an expression that denotes a special type name.
        /// </summary>
        public abstract SyntaxNode TypeExpression(SpecialType specialType);

        /// <summary>
        /// Creates an expression that denotes an array type.
        /// </summary>
        public abstract SyntaxNode ArrayTypeExpression(SyntaxNode type);

        /// <summary>
        /// Creates an expression that denotes a nullable type.
        /// </summary>
        public abstract SyntaxNode NullableTypeExpression(SyntaxNode type);

        /// <summary>
        /// Creates an expression that denotes an assignment from the right argument to left argument.
        /// </summary>
        public abstract SyntaxNode AssignmentStatement(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a value-type equality test operation.
        /// </summary>
        public abstract SyntaxNode ValueEqualsExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a reference-type equality test operation.
        /// </summary>
        public abstract SyntaxNode ReferenceEqualsExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a value-type inequality test operation.
        /// </summary>
        public abstract SyntaxNode ValueNotEqualsExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a reference-type inequality test operation.
        /// </summary>
        public abstract SyntaxNode ReferenceNotEqualsExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a less-than test operation.
        /// </summary>
        public abstract SyntaxNode LessThanExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a less-than-or-equal test operation.
        /// </summary>
        public abstract SyntaxNode LessThanOrEqualExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a greater-than test operation.
        /// </summary>
        public abstract SyntaxNode GreaterThanExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a greater-than-or-equal test operation.
        /// </summary>
        public abstract SyntaxNode GreaterThanOrEqualExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a unary negation operation.
        /// </summary>
        public abstract SyntaxNode NegateExpression(SyntaxNode expression);

        /// <summary>
        /// Creates an expression that denotes an addition operation.
        /// </summary>
        public abstract SyntaxNode AddExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes an subtraction operation.
        /// </summary>
        public abstract SyntaxNode SubtractExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a multiplication operation.
        /// </summary>
        public abstract SyntaxNode MultiplyExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a division operation.
        /// </summary>
        public abstract SyntaxNode DivideExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a modulo operation.
        /// </summary>
        public abstract SyntaxNode ModuloExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a bitwise-and operation.
        /// </summary>
        public abstract SyntaxNode BitwiseAndExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a bitwise-or operation.
        /// </summary>
        public abstract SyntaxNode BitwiseOrExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a bitwise-not operation
        /// </summary>
        public abstract SyntaxNode BitwiseNotExpression(SyntaxNode operand);

        /// <summary>
        /// Creates an expression that denotes a logical-and operation.
        /// </summary>
        public abstract SyntaxNode LogicalAndExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a logical-or operation.
        /// </summary>
        public abstract SyntaxNode LogicalOrExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates an expression that denotes a logical not operation.
        /// </summary>
        public abstract SyntaxNode LogicalNotExpression(SyntaxNode expression);

        /// <summary>
        /// Creates an expression that denotes a conditional evaluation operation.
        /// </summary>
        public abstract SyntaxNode ConditionalExpression(SyntaxNode condition, SyntaxNode whenTrue, SyntaxNode whenFalse);

        /// <summary>
        /// Creates an aexpression that denotes a coalesce operation. 
        /// </summary>
        public abstract SyntaxNode CoalesceExpression(SyntaxNode left, SyntaxNode right);

        /// <summary>
        /// Creates a member access expression.
        /// </summary>
        public abstract SyntaxNode MemberAccessExpression(SyntaxNode expression, SyntaxNode simpleName);

        /// <summary>
        /// Creates a member access expression.
        /// </summary>
        public SyntaxNode MemberAccessExpression(SyntaxNode expression, string identifer)
        {
            return MemberAccessExpression(expression, IdentifierName(identifer));
        }

        /// <summary>
        /// Creates an object creation expression.
        /// </summary>
        public abstract SyntaxNode ObjectCreationExpression(SyntaxNode namedType, IEnumerable<SyntaxNode> arguments);

        /// <summary>
        /// Creates an object creation expression.
        /// </summary>
        public SyntaxNode ObjectCreationExpression(ITypeSymbol type, IEnumerable<SyntaxNode> arguments)
        {
            return ObjectCreationExpression(TypeExpression(type), arguments);
        }

        /// <summary>
        /// Creates an object creation expression.
        /// </summary>
        public SyntaxNode ObjectCreationExpression(SyntaxNode type, params SyntaxNode[] arguments)
        {
            return ObjectCreationExpression(type, (IEnumerable<SyntaxNode>)arguments);
        }

        /// <summary>
        /// Creates an object creation expression.
        /// </summary>
        public SyntaxNode ObjectCreationExpression(ITypeSymbol type, params SyntaxNode[] arguments)
        {
            return ObjectCreationExpression(type, (IEnumerable<SyntaxNode>)arguments);
        }

        /// <summary>
        /// Creates a invocation expression.
        /// </summary>
        public abstract SyntaxNode InvocationExpression(SyntaxNode expression, IEnumerable<SyntaxNode> arguments);

        /// <summary>
        /// Creates a invocation expression
        /// </summary>
        public SyntaxNode InvocationExpression(SyntaxNode expression, params SyntaxNode[] arguments)
        {
            return InvocationExpression(expression, (IEnumerable<SyntaxNode>)arguments);
        }

        /// <summary>
        /// Creates a node that is an argument to an invocation.
        /// </summary>
        public abstract SyntaxNode Argument(string name, RefKind refKind, SyntaxNode expression);

        /// <summary>
        /// Creates a node that is an argument to an invocation.
        /// </summary>
        public SyntaxNode Argument(RefKind refKind, SyntaxNode expression)
        {
            return Argument(null, refKind, expression);
        }

        /// <summary>
        /// Creates a node that is an argument to an invocation.
        /// </summary>
        public SyntaxNode Argument(SyntaxNode expression)
        {
            return Argument(null, RefKind.None, expression);
        }

        /// <summary>
        /// Creates an expression that access an element of an array or indexer.
        /// </summary>
        public abstract SyntaxNode ElementAccessExpression(SyntaxNode expression, IEnumerable<SyntaxNode> arguments);

        /// <summary>
        /// Creates an expression that access an element of an array or indexer.
        /// </summary>
        public SyntaxNode ElementAccessExpression(SyntaxNode expression, params SyntaxNode[] arguments)
        {
            return ElementAccessExpression(expression, (IEnumerable<SyntaxNode>)arguments);
        }

        /// <summary>
        /// Creates an expression that denotes an is-type-check operation.
        /// </summary>
        public abstract SyntaxNode IsExpression(SyntaxNode expression, SyntaxNode type);

        /// <summary>
        /// Creates an expression that denotes an is-type-check operation.
        /// </summary>
        public SyntaxNode IsExpression(SyntaxNode expression, ITypeSymbol type)
        {
            return IsExpression(expression, TypeExpression(type));
        }

        /// <summary>
        /// Creates an expression that denotes an as-type operation.
        /// </summary>
        public abstract SyntaxNode AsExpression(SyntaxNode expression, SyntaxNode type);

        /// <summary>
        /// Creates an expression that denotes an as-type operation.
        /// </summary>
        public SyntaxNode AsExpression(SyntaxNode expression, ITypeSymbol type)
        {
            return AsExpression(expression, TypeExpression(type));
        }

        /// <summary>
        /// Creates an expression that denotes a runtime cast operation.
        /// </summary>
        public abstract SyntaxNode CastExpression(SyntaxNode type, SyntaxNode expression);

        /// <summary>
        /// Creates an expression that denotes a runtime cast operation.
        /// </summary>
        public SyntaxNode CastExpression(ITypeSymbol type, SyntaxNode expression)
        {
            return CastExpression(TypeExpression(type), expression);
        }

        /// <summary>
        /// Creates an expression that denotes a numerical conversion operation.
        /// </summary>
        public abstract SyntaxNode ConvertExpression(SyntaxNode type, SyntaxNode expression);

        /// <summary>
        /// Creates an expression that denotes a numerical conversion operation.
        /// </summary>
        public SyntaxNode ConvertExpression(ITypeSymbol type, SyntaxNode expression)
        {
            return ConvertExpression(TypeExpression(type), expression);
        }

        /// <summary>
        /// Creates an expression that declares a value returning lambda expression.
        /// </summary>
        public abstract SyntaxNode ValueReturningLambdaExpression(IEnumerable<SyntaxNode> lambdaParameters, SyntaxNode expression);

        /// <summary>
        /// Creates an expression that declares a void returning lambda expression
        /// </summary>
        public abstract SyntaxNode VoidReturningLambdaExpression(IEnumerable<SyntaxNode> lambdaParameters, SyntaxNode expression);

        /// <summary>
        /// Creates an expression that declares a value returning lambda expression.
        /// </summary>
        public abstract SyntaxNode ValueReturningLambdaExpression(IEnumerable<SyntaxNode> lambdaParameters, IEnumerable<SyntaxNode> statements);

        /// <summary>
        /// Creates an expression that declares a void returning lambda expression.
        /// </summary>
        public abstract SyntaxNode VoidReturningLambdaExpression(IEnumerable<SyntaxNode> lambdaParameters, IEnumerable<SyntaxNode> statements);

        /// <summary>
        /// Creates an expression that declares a single parameter value returning lambda expression.
        /// </summary>
        public SyntaxNode ValueReturningLambdaExpression(string parameterName, SyntaxNode expression)
        {
            return ValueReturningLambdaExpression(new[] { LambdaParameter(parameterName) }, expression);
        }

        /// <summary>
        /// Creates an expression that declares a single parameter void returning lambda expression.
        /// </summary>
        public SyntaxNode VoidReturningLambdaExpression(string parameterName, SyntaxNode expression)
        {
            return VoidReturningLambdaExpression(new[] { LambdaParameter(parameterName) }, expression);
        }

        /// <summary>
        /// Creates an expression that declares a single parameter value returning lambda expression.
        /// </summary>
        public SyntaxNode ValueReturningLambdaExpression(string parameterName, IEnumerable<SyntaxNode> statements)
        {
            return ValueReturningLambdaExpression(new[] { LambdaParameter(parameterName) }, statements);
        }

        /// <summary>
        /// Creates an expression that declares a single parameter void returning lambda expression.
        /// </summary>
        public SyntaxNode VoidReturningLambdaExpression(string parameterName, IEnumerable<SyntaxNode> statements)
        {
            return VoidReturningLambdaExpression(new[] { LambdaParameter(parameterName) }, statements);
        }

        /// <summary>
        /// Creates a lambda parameter.
        /// </summary>
        public abstract SyntaxNode LambdaParameter(string identifier, SyntaxNode type = null);

        /// <summary>
        /// Creates a lambda parameter.
        /// </summary>
        public SyntaxNode LambdaParameter(string identifier, ITypeSymbol type)
        {
            return LambdaParameter(identifier, TypeExpression(type));
        }
        #endregion
    }
}